/*
 * Copyright 2019 xiaomaoguai.com All right reserved. This software is the
 * confidential and proprietary information of xiaomaoguai.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with xiaomaoguai.com.
 */

package com.xiaomaoguai.tac.cloud.apm.logging.logback;

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Context;
import com.xiaomaoguai.datasecure.utils.SensitiveProcessUtils;
import com.xiaomaoguai.tac.cloud.apm.logging.logback.sensitive.SensitiveDataRuleManager;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.util.Set;

/**
 * 敏感信息数据转换器
 *
 * @author chenyao
 * @since 2019年1月21日 下午6:03:20
 */
@Setter
@Getter
public class SensitiveDataConverter extends MessageConverter {

	/**
	 * 日志脱敏开关
	 */
	private String allowRun;

	@Override
	public String convert(ILoggingEvent event) {
		if (getAllowRun() == null) {
			Context context = getContext();
			String allowRun = context.getProperty("SensitiveDataAllowRun");
			setAllowRun(StringUtils.defaultIfBlank(allowRun, "true"));
		}

		// 获取原始日志
		String formattedMessage = event.getFormattedMessage();

		// 获取脱敏后的日志
		return filterMessage(allowRun, formattedMessage);
	}

	/**
	 * 处理日志字符串，返回脱敏后的字符串
	 *
	 * @param allowRun 日志脱敏开关
	 * @param message  原始日志字符串
	 * @return 脱敏后的字符串
	 */
	public static String filterMessage(String allowRun, String message) {
		Set<SensitiveDataRule> sensitiveDataRules = SensitiveDataRuleManager.dataRules;
		if (!"true".equals(allowRun) || sensitiveDataRules == null || sensitiveDataRules.isEmpty()
				|| StringUtils.isEmpty(message)) {
			return message;
		}
		StringBuilder temp = new StringBuilder(message);
		for (SensitiveDataRule sensitiveDataRule : sensitiveDataRules) {
			String key = sensitiveDataRule.getFieldName();
			int index = -1;
			do {
				index = temp.indexOf(key, index + 1);
				if (index != -1) {
					// 判断key是否为单词字符
					if (isWordChar(temp, key, index)) {
						continue;
					}
					// 寻找值的开始位置
					int valueStart = getValueStartIndex(temp, index + key.length());
					// 查找值的结束位置（逗号，分号）
					int valueEnd = getValueEndEIndex(temp, valueStart);

					// 对获取的值进行脱敏
					String value = temp.substring(valueStart, valueEnd);
					String replace = SensitiveProcessUtils.shield(sensitiveDataRule.getFormat(), value);
					temp.replace(valueStart, valueEnd, replace);
				}
			} while (index != -1);
		}
		return temp.toString();
	}

	/**
	 * 判断从 {@code msg} 中获取的key值是否为单词，
	 *
	 * @param msg     完整字符串内容
	 * @param keyword 检查的关键词
	 * @param index   为key在msg中的索引值
	 * @return true/false
	 */
	private static boolean isWordChar(CharSequence msg, String keyword, int index) {
		// 必须确定key是一个单词
		if (index != 0) {
			// 判断key前面一个字符
			char pre = msg.charAt(index - 1);
			if ((pre > 47 && pre < 58) || (pre > 64 && pre < 91) || (pre > 96 && pre < 123)) {
				// 0-9 , A-Z, a-z
				return true;
			}
		}
		// 判断key后面一个字符
		char next = msg.charAt(index + keyword.length());
		if ((next > 47 && next < 58) || (next > 64 && next < 91) || (next > 96 && next < 123)) {
			// 0-9 , A-Z, a-z
			return true;
		}
		return false;
	}

	/**
	 * 获取value值的开始位置
	 *
	 * @param msg        要查找的字符串
	 * @param valueStart 查找的开始位置
	 * @return value值的开始位置
	 */
	private static int getValueStartIndex(CharSequence msg, int valueStart) {
		// 寻找值的开始位置
		do {
			char c = msg.charAt(valueStart);
			if (c == ':' || c == '=' || c == '：') {
				// key与 value的分隔符
				valueStart++;
				c = msg.charAt(valueStart);
				if (c == '"' || c == ' ') {
					valueStart++;
					c = msg.charAt(valueStart);
					if (c == '"' || c == ' ') {
						valueStart++;
					}
				}
				// 找到值的开始位置
				break;
			} else {
				valueStart++;
			}
		} while (true);
		return valueStart;
	}

	/**
	 * 获取value值的结束位置
	 *
	 * @param msg      要查找的字符串
	 * @param valueEnd 查找的开始位置
	 * @return value值的结束位置
	 */
	private static int getValueEndEIndex(CharSequence msg, int valueEnd) {
		do {
			if (valueEnd == msg.length()) {
				break;
			}
			char c = msg.charAt(valueEnd);

			if (c == '"') {
				// 引号时，判断下一个值是结束，分号还是逗号决定是否为值的结束
				if (valueEnd + 1 == msg.length()) {
					break;
				}
				char next = msg.charAt(valueEnd + 1);
				if (next == ';' || next == ',' || next == '}' || next == ' ') {
					break;
				} else {
					valueEnd++;
				}
			} else if (c == ';' || c == ',' || c == '}' || c == '\n' || c == '\t') {
				break;
			} else {
				valueEnd++;
			}

		} while (true);
		return valueEnd;
	}

}
